#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "validation.h"

#if ON_PC
#define MAX_POINTS 8192
#define MAX_LEADS 2

int clead = 0; /* current lead */

struct point {
	offset_t offset;
	char symbol; /* For validation data, original symbol */
	int matched;
};

/* Detected points */
struct point pts[MAX_LEADS][MAX_WAVE][TYPE_MAX][MAX_POINTS];
int nr_pts[MAX_LEADS][MAX_WAVE][TYPE_MAX];

/* Validated points */
struct point val[MAX_WAVE][TYPE_MAX][MAX_POINTS];
int nr_val[MAX_WAVE][TYPE_MAX];

/* Validated points ranges */
#define MAX_RANGES 128
offset_t ranges[MAX_RANGES][2];
int nr_ranges = 0;

void add_point(offset_t offset, enum wave wave, enum type type) {
	pts[clead][wave][type][nr_pts[clead][wave][type]].offset = offset;
	pts[clead][wave][type][nr_pts[clead][wave][type]].matched = 0;
	fprintf(stderr, "Added point %d @ %d (%d %d)\n",
					nr_pts[clead][wave][type], offset, wave, type);
/*	if (wave == qrs_wave && type == type_peak)
		printf("#VALIDATION: Added point %d @ %d (%d %d)\n",
					nr_pts[clead][wave][type], offset, wave, type);*/
	nr_pts[clead][wave][type]++;
}

void dump_points(char* varname, enum wave wave, enum type type) {
	int i;

	printf("%s = [ ", varname);

	for (i = 0; i < nr_pts[clead][wave][type]; i++) {
		printf("%d ", pts[clead][wave][type][i].offset);
	}

	printf("];\n");
}


int data_offset;

void add_val_point(int offset, char symbol, enum wave wave, enum type type) {
	
	val[wave][type][nr_val[wave][type]].offset = offset;
	val[wave][type][nr_val[wave][type]].symbol = symbol;
	val[wave][type][nr_val[wave][type]].matched = 0;
/*	fprintf(stderr, "Added point %d @ %d (%d %d)\n",
				nr_val[wave][type], offset, wave, type);*/
	//printf("#VALIDATION*-*offset now = %d (%d %d %d)\n", data_offset, offset, wave, type);
	nr_val[wave][type]++;
}

void dump_val() {
	enum wave wave;
	enum type type;
	int i;
	char* sval = malloc(65536);
	char* sval_i = sval;
	char* schr = malloc(65536);
	char* schr_i = schr;

	sval += sprintf(sval, "val = [ ");

	for (wave = 0; wave < MAX_WAVE; wave++) {
		for (type = 0; type < TYPE_MAX; type++) {
			for (i = 0; i < nr_val[wave][type]; i++) {
//				fprintf(stderr, "off=%d dat_off=%d size=%d\n", val[wave][type][i].offset, data_offset, size);
				if (val[wave][type][i].offset < last_offset) {
					sval += sprintf(sval, "%d ", val[wave][type][i].offset);
					*schr++ = val[wave][type][i].symbol;
				}
			}
		}
	}

	sval += sprintf(sval, "];\n");
	*schr++ = 0;
	printf(sval_i);
	printf("valchr = [ \"%s\" ];\n", schr_i);

	free(sval_i);
	free(schr_i);
}

char* filename_save;

void read_validation(char* filename, int offset) {
	char buffer[128];
	FILE* valid = fopen(filename, "r");
	int last_off = 0;
	char last_c = ' ';
	enum wave last_wave = MAX_WAVE;
	int range_start = 0;

	filename_save = filename;

	data_offset = offset;
	//printf("#VALIDATION*-*offset = %d\n", data_offset);

	while(1) {
		if (!fgets(buffer, 128, valid)) {
			break;
		}
		int off = atoi(strtok(buffer, " "));
		char c = strtok(NULL, " ")[0];
		enum wave wave = MAX_WAVE;

		if (off-last_off > 150) {
			if (range_start > 0) {
				ranges[nr_ranges][0] = range_start;
				ranges[nr_ranges][1] = last_off-offset;
				range_start = off-offset;
				/*printf("#VALIDATION: Range %d-%d\n",
						ranges[nr_ranges][0]+data_offset,
						ranges[nr_ranges][1]+data_offset);*/
				nr_ranges++;
			}
		}

		if (range_start == 0)
			range_start = off-offset;

		switch(c) {
		case 'p':
			wave = p_wave;
			break;
		case 't':
			wave = t_wave;
			break;
		case 'A': /* Is A really like a N? */
		case 'B': /* Is B really like a N? */
		case 'Q': /* Is Q really like a N? */
		case 'V':
		case 'N':
			wave = qrs_wave;
			break;
		case 'u':
			wave = u_wave;
			break;
		case ')':
			add_val_point(off-offset, c, last_wave, type_end);
			last_off = off;
			last_c = c;
			last_wave = MAX_WAVE;
			continue;
		case '(':
			last_off = off;
			last_c = c;
			last_wave = MAX_WAVE;
			continue;
		default:
			printf("#VALIDATION: Weird symbol in validation@%d: %c\n", off, c);
		}

		add_val_point(off-offset, c, wave, type_peak);
		if (last_c == '(')
			add_val_point(last_off-offset, last_c, wave, type_onset);			

		last_off = off;
		last_c = c;
		last_wave = wave;
	}

	ranges[nr_ranges][0] = range_start;
	ranges[nr_ranges][1] = last_off-offset;
	/*printf("#VALIDATION: Range %d-%d\n",
			ranges[nr_ranges][0]+data_offset,
			ranges[nr_ranges][1]+data_offset);*/
	nr_ranges++;

	//printf("#VALIDATION*-*offset now = %d\n", data_offset);

	fclose(valid);
}

static int in_ranges(offset_t pt, int d1, int d2) {
	int i;

	if (pt > last_offset)
		return 0;

	for (i = 0; i < nr_ranges; i++) {
		if (pt >= ranges[i][0]-d1 && pt <= ranges[i][1]+d2)
			return 1;
	}
	
	return 0;
}

void check_val(char* name, enum wave wave, enum type type) {
	int i, j, lead;
	int ok = 0;
	int fp = 0;
	int fn = 0;
	int sum = 0;
	int var = 0;

#define DEBUG 1

#if DEBUG
	enum wave dbg_wave = qrs_wave;
	enum type dbg_type = type_onset;
#endif

	for (i = 0; i < nr_val[wave][type]; i++) {
#if DEBUG
		if (wave == dbg_wave && type == dbg_type)
			printf("#VALIDATION #- fitting %d(%d) (%d/%d)\n", val[wave][type][i].offset, val[wave][type][i].offset+data_offset, i+1, nr_val[wave][type]);
#endif
		struct point* closest[MAX_LEADS] = {NULL, NULL};
		struct point* allclosest = NULL;
		/* For each validation point, find the closest points, within 80 samples,
		 * on each lead. */
		for (lead = 0; lead <= clead; lead++) {
			for (j = 0; j < nr_pts[lead][wave][type]; j++) {
				if (pts[lead][wave][type][j].matched) continue;
#if 0
				if (wave == dbg_wave && type == dbg_type)
					printf("#VALIDATION #- try %d (lead=%d)\n", pts[lead][wave][type][j].offset, lead);
#endif				
				if (abs(pts[lead][wave][type][j].offset-val[wave][type][i].offset) < 80) {
					if (!closest[lead]) {
						closest[lead] = &pts[lead][wave][type][j];
					}
					else if (abs(pts[lead][wave][type][j].offset-val[wave][type][i].offset) <
						 abs(closest[lead]->offset-val[wave][type][i].offset)) {
						closest[lead] = &pts[lead][wave][type][j];
					}
				}	
			}
		}

		for (lead = 0; lead <= clead; lead++) {
			if (closest[lead]) {
				//printf("#VALIDATION #- Closest on lead %d: %d (%d)\n", lead, closest[lead]->offset, val[wave][type][i].offset);
				closest[lead]->matched = 1;
				if (!allclosest) {
					allclosest = closest[lead];
				}
				else if (abs(closest[lead]->offset-val[wave][type][i].offset) <
					 abs(allclosest->offset-val[wave][type][i].offset)) {
					allclosest = closest[lead];
				}
			}
		}

		if (allclosest) {
			val[wave][type][i].matched = 1;
			int delta = allclosest->offset-val[wave][type][i].offset;
#if DEBUG
			if (wave == dbg_wave && type == dbg_type)
				printf("#VALIDATION # Matching %d and %d (%d)\n", allclosest->offset, val[wave][type][i].offset, delta);
#endif
			sum += delta;
			var += delta*delta;
			ok++;
		}
	}

#if 0
	for (i = 0; i < nr_pts[clead][wave][type]; i++) {
#if DEBUG
		if (wave == dbg_wave && type == dbg_type)
			printf("#VALIDATION #- fitting: %d (%d/%d)\n", pts[wave][type][i].offset+data_offset, i+1, nr_pts[wave][type]);
#endif
		for (j = 0; j < nr_val[wave][type]; j++) {
			if (abs(pts[wave][type][i].offset-val[wave][type][j].offset) < 80 &&
					abs(pts[wave][type][i].offset-val[wave][type][j].offset) <
						abs(pts[wave][type][i].offset-val[wave][type][j+1].offset) &&
					abs(pts[wave][type][i].offset-val[wave][type][j].offset) <
						abs(pts[wave][type][i+1].offset-val[wave][type][j].offset) &&
					!val[wave][type][j].matched) {
				pts[wave][type][i].matched = 1;
				val[wave][type][j].matched = 1;
/*				if (wave == qrs_wave && type == type_onset)
					printf("#VALIDATION - fit: %d %d\n",
						pts[wave][type][i].offset+data_offset, val[wave][type][j].offset+data_offset);*/
				int delta = pts[wave][type][i].offset-val[wave][type][j].offset;
				sum += delta;
				var += delta*delta;
#if DEBUG
				if (wave == dbg_wave && type == dbg_type)
					printf("#VALIDATION: #%d delta=%d, sum=%d, var=%d\n", pts[wave][type][i].offset+data_offset, delta, sum, var);
#endif
				ok++;
				break;
			}
		}
	}
#endif

	for (i = 0; i < nr_pts[clead][wave][type]; i++) {
		if (!pts[clead][wave][type][i].matched) {
/*			if (type == type_end) {
				if (in_ranges(pts[clead][wave][type][i].offset, 0, 20)) {
					printf("#FP(%s): %d\n", name, pts[clead][wave][type][i].offset+data_offset);
					fp++;
				}
			}
			else if (type == type_onset) {
				if (in_ranges(pts[clead][wave][type][i].offset, 20, 0)) {
					printf("#FP(%s): %d\n", name, pts[clead][wave][type][i].offset+data_offset);
					fp++;
				}
			}
			else {*/
				if (in_ranges(pts[clead][wave][type][i].offset, 0, 0)) {
					printf("#FP(%s): %d\n", name, pts[clead][wave][type][i].offset+data_offset);
					fp++;
				}
				//}
		}
	}

	for (i = 0; i < nr_val[wave][type]; i++) {
		if (!val[wave][type][i].matched) {
			printf("#FN(%s): %d\n", name, val[wave][type][i].offset+data_offset);
			fn++;
		}
	}

	printf("#VALIDATION: %s += [ %d %d %d ]; #%s\n", name, ok, fp, fn, filename_save);
	printf("#VALIDATION: %s_sum += %d;  #%s\n", name, sum, filename_save);
	double avg = (double)sum/ok;
	printf("#VALIDATION: %s_std = [ %s_std %lf ];  #%s\n", name, name,
			ok > 0 ? sqrt((double)var/(double)ok - avg*avg) : 0.0, filename_save);
}

void check_validation() {
	check_val("qrs", qrs_wave, type_peak);
	check_val("qrs_on", qrs_wave, type_onset);
	check_val("qrs_end", qrs_wave, type_end);

	check_val("p", p_wave, type_peak);
	check_val("p_on", p_wave, type_onset);
	check_val("p_end", p_wave, type_end);

	check_val("t", t_wave, type_peak);
	check_val("t_end", t_wave, type_end);
}

void set_lead(int i) {
	clead = i;
}

#endif
